#include "nurbs.h"

Nurbs::Nurbs(double degree)
    : degree(degree)
{
    //degree       = 3; // Na razie mamy tylko krzywe stopnia 3.
    num_of_knots = 1;

    for (int i = 0; i <= degree; i++)
    {
        knots.push_back(0.0);
        knots.push_back(1.0);
    }

    UpdateKnots();

    options.draw_control_points   = YES;
    options.draw_control_polyline = NO;
    options.draw_curve            = YES;
}

/* Dodawanie punktu de Boora o okreslonej wadze na koniec listy punktow. */
void Nurbs::AddControlPoint(QPointF dbPoint, double weight)
{
    /* Dodajemy punkt i wage. */
    control_points.push_back(dbPoint);
    weights.push_back(weight);

    if ( control_points.size() > (unsigned int)degree )
    {
        /* Powiekszamy rozmiar listy wezlow. */
        //knots.reserve(knots.size() + 1); // chyba lepiej niz dodawanie bezsensownego elementu ??
        knots.push_back(1);
        num_of_knots++;

        /* Aktualizujemy wartosci na skrajach przedzialow. */
        UpdateKnots();
    }
}

/* Przemieszczanie punktu de Boora */
void Nurbs::MovePoint(int index, QPointF new_position)
{
    control_points[index] = new_position;
}

/* Przesuwamy cala krzywa o wektor. */
void Nurbs::MoveCurve(QPointF vec)
{
    for (unsigned int i = 0; i < control_points.size(); i++)
        control_points[i] += vec;
}

/* Aktualizacja wezlow. */
void Nurbs::UpdateKnots()
{
    for (int i = 0; i <= degree; i++)
        knots[i] = 0.0;

    for (int i = degree + 1; i < num_of_knots + degree - 1; i++)
        knots[i] = (double)(i - degree) / (double)(num_of_knots - 1);

    for (int i = num_of_knots + degree - 1; i < num_of_knots + 2 * degree; i++)
        knots[i] = 1.0;
}

/* Szukanie przedzialu, do ktorego wpada t. */
int Nurbs::FindIndex(double t)
{
    for (int i = degree + 1; i <= num_of_knots + degree - 1; i++)
    {
        if (t <= (knots[i]))
            return i - degree - 1;
    }

    return -1;
}

/* Implementacja wymiernego algorytmu deBoora-Coxa. */
QPointF Nurbs::Eval(double t)
{
    assert(0 <= t && t <= 1);

    int j = FindIndex(t);

    /* t nie moze wpadac do tego przedzialu && wartosc nie wpada do zadnego przedzialu. */
    assert( (unsigned int)j <= control_points.size( ) - degree && j != -1 );

    QPointF **W;
    double **gamma, **alpha;

    W     = new QPointF*[j + degree + 1];
    gamma = new  double*[j + degree + 1];
    alpha = new  double*[j + degree + 1];

    for (int i = 0; i < j + degree + 1; i++)
    {
        W[i]     = new QPointF[j + degree + 1];
        gamma[i] = new  double[j + degree + 1];
        alpha[i] = new  double[j + degree + 1];;
    }

    for (int i = j; i <= j + degree; i++)
    {
        W[i][0]     = control_points[i];
        gamma[i][0] = weights[i];
    }

    for (int k = 1; k <= degree; k++)
    {
        for (int i = j + k; i <= j + degree; i++)
        {
            alpha[i][k] = (t - knots[i]) / (knots[i + degree + 1 - k] - knots[i]);
            gamma[i][k] = alpha[i][k] * gamma[i][k - 1] + (1.0 - alpha[i][k]) * gamma[i - 1][k - 1];

            QPointF p1, p2;

            p1 = W[i    ][k - 1] * (alpha[i][k] * gamma[i][k - 1] / gamma[i][k]);
            p2 = W[i - 1][k - 1] * ((1.0 - alpha[i][k]) * gamma[i - 1][k - 1] / gamma[i][k]);

            W[i][k] = p1 + p2;
        }
    }

    return W[j + degree][degree];
}

/* Metoda tworzaca tablice n kolejnych rownoodleglych punktow na kazdym przedziale krzywej. */
vector< QPointF > Nurbs::ComputePoints( int numberOfPoints )
{
    int n           = ( num_of_knots - 1 ) * numberOfPoints;
    vector< QPointF > points( n );

    for ( int i = 0; i < n; i++ )
        points[i] = Eval( (double)i / (double)( n - 1 ) );

    return points;
}

/* Rysujemy koleczko :) */
void Nurbs::GlCircle3i(GLint x, GLint y, GLint radius)
{
    float angle;

    glPushMatrix();
    glLoadIdentity();

    glColor3f(0.0f, 1.0f, 0.0f);
    glLineWidth(2.0f);

    glBegin(GL_POLYGON);
        for(int i = 0; i < 100; i++)
        {
            angle = i * 2.0 * M_PI / 100.0;
            glVertex2f(x + (cos(angle) * radius), y + (sin(angle) * radius));
        }
    glEnd();

    glPopMatrix();
}

/* Rysujamy krzywa. */
void Nurbs::DrawCurve( )
{
    int numberOfPoints      = 10;                                  // Liczba punktow w przedziale.
    int allPoints           = numberOfPoints * (num_of_knots - 1); // Wszystkie punkty krzywej.
    vector< QPointF > computedPoints( ComputePoints(numberOfPoints) );
    float x, y;

    glColor3f(0.0f, 0.0f, 1.0f);
    glLineWidth(1.0f);

    glBegin(GL_LINE_STRIP);
        // Rysujemy krzywa
        for (int i = 0; i < allPoints; i++)
        {
            x = (GLfloat)computedPoints[i].x();
            y = (GLfloat)computedPoints[i].y();

            glVertex2f(x, y);
        }
    glEnd();
}

/* Rysujemy punkty kontrolne. */
void Nurbs::DrawControlPoints()
{
    float x, y;

    glBegin(GL_POINT);
        // Rysujemy punkty de Boora.
        for (unsigned int i = 0; i < control_points.size(); i++)
        {
            x = (GLfloat)control_points[i].x();
            y = (GLfloat)control_points[i].y();

            glPushName(i);
            GlCircle3i(x, y, 5);
            glPopName();
        }
    glEnd();
}

/* Rysujemy krzywa kontrolna. */
void Nurbs::DrawControlPolyline()
{
    float x, y;

    glColor3f(0.0f, 0.0f, 1.0f);
    glLineWidth(1.0f);
    glBegin(GL_LINE_STRIP);
        // Rysujemy lamana kontrolna
        for (unsigned int i = 0; i < control_points.size(); i++)
        {
            x = (GLfloat)control_points[i].x();
            y = (GLfloat)control_points[i].y();

            glVertex2f(x, y);
        }
    glEnd();
}

/* Metoda rysujaca krzywa. */
void Nurbs::Draw()
{
    if (control_points.size() > 0)
    {
        if (options.draw_curve)
            DrawCurve();

        if (options.draw_control_polyline)
            DrawControlPolyline();

        if (options.draw_control_points)
            DrawControlPoints();
    }
}

